/**
 * Copyright 2008 floop.pl
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 *  
 * http://www.apache.org/licenses/LICENSE-2.0 
 *  
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package org.eaicompiler.decompiler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eaicompiler.database.ScriptdevHibernateUtil;
import org.eaicompiler.database.model.EventaiScripts;
import org.eaicompiler.database.model.LocalizedTexts;
import org.eaicompiler.model.Action;
import org.eaicompiler.model.Defines;
import org.eaicompiler.model.Event;
import org.eaicompiler.model.Target;
import org.eaicompiler.sqlgen.builders.SQLBuilder;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

/**
 * Script decompiler, woohoo :)
 * @author floop.pl
 */
public class Decompiler {
	private SessionFactory sf;
	private Session session;
	private Defines defines;
	
	private Map<Integer, List<Event>> eventsForCreature;
	
	public Decompiler() {
		super();
		sf = ScriptdevHibernateUtil.getSessionFactory();
		session = sf.openSession();
		eventsForCreature = new HashMap<Integer, List<Event>>();
		defines = new Defines();
	}

	private List<Event> getEventsForCreature(int id)
	{
		List<Event> result = null;
		result = eventsForCreature.get(new Integer(id));
		if(result==null)
		{
			eventsForCreature.put(new Integer(id), new ArrayList<Event>());
			result = eventsForCreature.get(new Integer(id));
		}
		return result;
	}
	
	@SuppressWarnings("unchecked")
	public String decompile(String selector)
	{
		StringBuffer creatures = new StringBuffer();
		StringBuffer result = new StringBuffer();
		List list = session.createQuery("from EventaiScripts " + selector + " order by creatureId, event_inverse_phase_mask").list();
		Iterator it = list.iterator();
		while(it.hasNext())
		{
			EventaiScripts l = (EventaiScripts) it.next();
			Event e = convertEvent(l);
			getEventsForCreature(l.getCreatureId()).add(e);
		}
		Iterator<Integer> i = eventsForCreature.keySet().iterator();
		while(i.hasNext())
		{
			Integer creature = i.next();
			creatures.append(decompileCreature(creature.intValue(), eventsForCreature.get(creature)));
			creatures.append("\n");
		}		
		result.append(decompileDefines());
		result.append("\n");
		result.append(creatures);		
		return result.toString();
	}
	
	private String decompileDefines()
	{
		StringBuffer result = new StringBuffer();		
		for(int f=0;f<Defines.DEFINE_TYPES_COUNT;f++)
		{
			result.append(decompileDefinesFor(f));
		}
		return result.toString();
	}
	
	private String decompileDefinesFor(int definesType)
	{
		StringBuffer result = new StringBuffer();
		Map<String, Integer> dm = defines.getDefines(definesType);
		Iterator<String> it = dm.keySet().iterator();
		while(it.hasNext())
		{
			String d = it.next();
			switch(definesType)
			{
				case Defines.DEFINE_CREATURE:
				{
					result.append("DEFINE CREATURE ");
				}
				break;
				case Defines.DEFINE_EMOTE:
				{
					result.append("DEFINE EMOTE ");
				}
				break;
				case Defines.DEFINE_FIELD:
				{
					result.append("DEFINE FIELD ");
				}
				break;
				case Defines.DEFINE_FLAG:
				{
					result.append("DEFINE FLAG ");
				}
				break;
				case Defines.DEFINE_QUEST:
				{
					result.append("DEFINE QUEST ");
				}
				break;
				case Defines.DEFINE_SOUND:
				{
					result.append("DEFINE SOUND ");
				}
				break;
				case Defines.DEFINE_SPELL:
				{
					result.append("DEFINE SPELL ");
				}
				break;
			}
			result.append(dm.get(d));
			result.append(" ");
			result.append(d);
			result.append("\n");
		}
		return result.toString();		
	}
	
	/**
	 * Method that converts the event from database into internal structure
	 * @param Event in database
	 * @return Event in internal structure format
	 */
	private Event convertEvent(EventaiScripts from) {
		Event to = new Event();
		to.setChance(from.getEventChance());
		to.setInversePhaseMask(from.getEventInversePhaseMask());
		to.setType(from.getEventType());
		to.setComment(from.getComment());
		to.setParam(0, from.getEventParam1());
		to.setParam(1, from.getEventParam2());
		to.setParam(2, from.getEventParam3());
		to.addAction(createAction(from.getAction1Type(), from.getAction1Param1(), from.getAction1Param2(), from.getAction1Param3()));
		to.addAction(createAction(from.getAction2Type(), from.getAction2Param1(), from.getAction2Param2(), from.getAction2Param3()));
		to.addAction(createAction(from.getAction3Type(), from.getAction3Param1(), from.getAction3Param2(), from.getAction3Param3()));
		return to;
	}
	
	private Action createAction(int type, int param1, int param2, int param3)
	{
		Action a = new Action();
		a.setType(type);
		a.setParam(0, param1);
		a.setParam(1, param2);
		a.setParam(2, param3);
		return a;
	}
	
	private String decompileCreature(int creatureId, List<Event> events)
	{
		StringBuffer result = new StringBuffer();
		// First, we should calculate maximum phase used
		long mask = 0;
		int maxPhase = 0;
		Iterator<Event> it = events.iterator();
		while(it.hasNext())
		{
			Event e = it.next();
			mask = mask | e.getInversePhaseMask();
		}
		if(mask>0)
		{
			for(int f=0;f<32;f++)
			{
				if((mask & 1<<f)>0)
				{
					maxPhase = f+1;
				}
			}
		}
		long currentMask = -1;
		
		result.append("SCRIPT FOR CREATURE ");
		result.append(creatureId);
		result.append("\n");		
		
		it = events.iterator();
		while(it.hasNext())
		{
			Event e = it.next();
			if(e.getInversePhaseMask()!=currentMask)
			{
				if(e.getInversePhaseMask()==0)
				{
					result.append("PHASE ALL\n");
				} else {
					result.append("PHASE ");
					result.append(calculatePhase(e.getInversePhaseMask(), maxPhase));
					result.append("\n");
				}
				currentMask = e.getInversePhaseMask();
			}
			result.append(decompileEvent(e));
		}				
						
		return result.toString();
	}
	
	private String calculatePhase(long phase, int maxPhase)
	{
		StringBuffer result = new StringBuffer();
		boolean hasPhase = false;
		for(int f=0;f<maxPhase;f++)
		{
			if((phase & 1<<f)==0)
			{
				if(hasPhase)
				{
					result.append(", ");					
				}
				result.append(f+1);
				hasPhase = true;
			}
		}
		return result.toString();
	}
		
	private String decompileEvent(Event e)
	{
		StringBuffer result = new StringBuffer();
		result.append("\tEVENT ");
		if(e.getChance()<100)
		{
			result.append("CHANCE ");
			result.append(e.getChance());
			result.append("% ");
		}
		result.append("ON ");
		switch(e.getType())
		{
			case Event.EVENT_T_AGGRO:
			{
				result.append("AGGRO");
			}
			break;
			case Event.EVENT_T_DEATH:
			{
				result.append("DEATH");
			}
			break;
			case Event.EVENT_T_EVADE:
			{
				result.append("EVADE");
			}
			break;
			case Event.EVENT_T_HP:
			{
				result.append("HP ");
				result.append(convertToPercent(e.getParam(0)));
				if(e.getParam(1)>0)
				{
					result.append(" FROM ");
					result.append(convertToPercent(e.getParam(1)));
				}
				if(e.getParam(2)>0)
				{
					result.append(" FOR ");
					result.append(convertToTime(e.getParam(2)));
				}
			}
			break;
			case Event.EVENT_T_KILL:
			{
				result.append("KILL");
				if(e.getParam(0)>0)
				{
					result.append(" AFTER ");
					result.append(convertToTime(e.getParam(0)));
				}
			}
			break;
			case Event.EVENT_T_MANA:
			{
				result.append("MANA ");
				result.append(convertToPercent(e.getParam(0)));
				if(e.getParam(1)>0)
				{
					result.append(" FROM ");
					result.append(convertToPercent(e.getParam(1)));
				}
				if(e.getParam(2)>0)
				{
					result.append(" FOR ");
					result.append(convertToTime(e.getParam(2)));
				}
			}
			break;
			case Event.EVENT_T_SPELLHIT:
			{
				result.append("SPELLHIT");
				if(e.getParam(1)>0)
				{
					result.append(" EVERY ");
					result.append(convertToTime(e.getParam(1)));
				}
				if(e.getParam(0)>0)
				{
					result.append(" SPELL ");
					String spell = "\"SPELL_" + e.getParam(2) + "\"";
					defines.define(Defines.DEFINE_SPELL, spell, e.getParam(2));
					result.append(spell);
				}
			}
			break;
			case Event.EVENT_T_TIMER_OOC_REPEAT:
			{
				result.append("TIMER OOC REPEAT EVERY ");
				result.append(convertToTime(e.getParam(0)));
				if(e.getParam(1)>0)
				{
					result.append(" AFTER ");
					result.append(convertToTime(e.getParam(1)));
				}
				if(e.getParam(2)>0)
				{
					result.append(" RANDOM ");
					result.append(convertToTime(e.getParam(2)));
				}
			}
			break;
			case Event.EVENT_T_TIMER_OOC_SINGLE:
			{
				result.append("TIMER OOC SINGLE");
				if(e.getParam(0)>0)
				{
					result.append(" AFTER ");
					result.append(convertToTime(e.getParam(0)));
				}
			}
			break;
			case Event.EVENT_T_TIMER_REPEAT:
			{
				result.append("TIMER REPEAT EVERY ");
				result.append(convertToTime(e.getParam(0)));
				if(e.getParam(1)>0)
				{
					result.append(" AFTER ");
					result.append(convertToTime(e.getParam(1)));
				}
				if(e.getParam(2)>0)
				{
					result.append(" RANDOM ");
					result.append(convertToTime(e.getParam(2)));
				}
			}
			break;
			case Event.EVENT_T_TIMER_SINGLE:
			{
				result.append("TIMER SINGLE");
				if(e.getParam(0)>0)
				{
					result.append(" AFTER ");
					result.append(convertToTime(e.getParam(0)));
				}
			}
			break;
		}
		result.append("\n");
		if(e.getComment()!=null && e.getComment().length()>0)
		{
			result.append("\t\tCOMMENT \"");
			result.append(e.getComment());
			result.append("\"\n");
		}
		for(int f=0;f<Event.MAX_EVENT_ACTIONS;f++)
		{
			result.append(decompileAction(e.getAction(f)));			
		}
		
		result.append("\tEND\n");
		return result.toString();
	}
	
	private String decompileAction(Action action) {
		StringBuffer s = new StringBuffer();
		if(action.getType()>0)
		{
			s.append("\t\t");
			switch(action.getType())
			{
				case Action.ACTION_T_SAY:
				{
					s.append("SAY ");
					s.append(getDecompiledText(action.getParam(0)));					
				}
				break;
				case Action.ACTION_T_RANDOM_SAY:
				{
					s.append("SAY ");
					s.append(getDecompiledText(action.getParam(0)));
					if(action.getParam(1)!=-1)
					{
						s.append(" OR ");
						s.append(getDecompiledText(action.getParam(1)));
					}
					if(action.getParam(2)!=-1)
					{
						s.append(" OR ");
						s.append(getDecompiledText(action.getParam(2)));
					}
				}
				break;
				case Action.ACTION_T_YELL:
				{
					s.append("YELL ");
					s.append(getDecompiledText(action.getParam(0)));					
				}
				break;
				case Action.ACTION_T_RANDOM_YELL:
				{
					s.append("YELL ");
					s.append(getDecompiledText(action.getParam(0)));
					if(action.getParam(1)!=-1)
					{
						s.append(" OR ");
						s.append(getDecompiledText(action.getParam(1)));
					}
					if(action.getParam(2)!=-1)
					{
						s.append(" OR ");
						s.append(getDecompiledText(action.getParam(2)));
					}
				}
				break;
				case Action.ACTION_T_TEXTEMOTE:
				{
					s.append("TEXT EMOTE ");
					s.append(getDecompiledText(action.getParam(0)));					
				}
				break;
				case Action.ACTION_T_RANDOM_TEXTEMOTE:
				{
					s.append("TEXT EMOTE ");
					s.append(getDecompiledText(action.getParam(0)));
					if(action.getParam(1)!=-1)
					{
						s.append(" OR ");
						s.append(getDecompiledText(action.getParam(1)));
					}
					if(action.getParam(2)!=-1)
					{
						s.append(" OR ");
						s.append(getDecompiledText(action.getParam(2)));
					}
				}
				break;
				case Action.ACTION_T_AUTO_ATTACK:
				{
					s.append("AUTO ATTACK ");
					if(action.getParam(0)==0)
					{
					 	s.append("OFF");
					} else {
					 	s.append("ON");						
					}
				}
				break;
				case Action.ACTION_T_CAST:
				{
					s.append("CAST ");
					String spell = "\"SPELL_" + action.getParam(0) + "\"";
					defines.define(Defines.DEFINE_SPELL, spell, action.getParam(0));
					s.append(spell);
					s.append(" ");
					if(action.getParam(2)!=0)
					{
						s.append("FORCE ");
					}
					s.append(decompileTargetCode(action.getParam(1)));
				}
				break;
				case Action.ACTION_T_CASTCREATUREGO:
				{
					s.append("CAST CREATURE GO ");
					String quest = "\"QUEST_" + action.getParam(0) + "\"";
					defines.define(Defines.DEFINE_QUEST, quest, action.getParam(0));
					s.append(quest);
					s.append(" SPELL ");
					String spell = "\"SPELL_" + action.getParam(1) + "\"";
					defines.define(Defines.DEFINE_SPELL, spell, action.getParam(1));
					s.append(spell);
					s.append(" ON ");
					s.append(decompileTargetCode(action.getParam(2)));
				}
				break;
				case Action.ACTION_T_CASTCREATUREGO_ALL:
				{
					s.append("CAST CREATURE GO ");
					String quest = "\"QUEST_" + action.getParam(0) + "\"";
					defines.define(Defines.DEFINE_QUEST, quest, action.getParam(0));
					s.append(quest);
					s.append(" SPELL ");
					String spell = "\"SPELL_" + action.getParam(1) + "\"";
					defines.define(Defines.DEFINE_SPELL, spell, action.getParam(1));
					s.append(spell);
					s.append(" ALL");
				}
				break;
				case Action.ACTION_T_COMBAT_MOVEMENT:
				{
					s.append("COMBAT MOVEMENT ");
					if(action.getParam(0)==0)
					{
					 	s.append("OFF");
					} else {
					 	s.append("ON");						
					}
				}
				break;
				case Action.ACTION_T_EMOTE:
				{
					s.append("EMOTE ");
					String e = "\"EMOTE_" + action.getParam(0) + "\"";
					defines.define(Defines.DEFINE_EMOTE, e, action.getParam(0));
					s.append(e);					
				}
				break;
				case Action.ACTION_T_RANDOM_EMOTE:
				{
					s.append("EMOTE ");
					String e = "\"EMOTE_" + action.getParam(0) + "\"";
					defines.define(Defines.DEFINE_EMOTE, e, action.getParam(0));
					s.append(e);
					if(action.getParam(1)!=-1)
					{
						s.append(" OR ");
						e = "\"EMOTE_" + action.getParam(1) + "\"";
						defines.define(Defines.DEFINE_EMOTE, e, action.getParam(1));
						s.append(e);
					}
					if(action.getParam(2)!=-1)
					{
						s.append(" OR ");
						e = "\"EMOTE_" + action.getParam(2) + "\"";
						defines.define(Defines.DEFINE_EMOTE, e, action.getParam(2));
						s.append(e);
					}
				}
				break;
				case Action.ACTION_T_EVADE:
				{
					s.append("EVADE");
				}
				break;
				case Action.ACTION_T_FLEE:
				{
					s.append("FLEE");
				}
				break;				
				case Action.ACTION_T_INC_PHASE:
				{
					if(action.getParam(0)>0)
					{
						s.append("SET PHASE INC ");
						s.append(action.getParam(0));
					} else {
						s.append("SET PHASE DEC ");
						s.append(-action.getParam(0));						
					}
				}
				break;				
				case Action.ACTION_T_SET_PHASE:
				{
					s.append("SET PHASE TO ");
					s.append(action.getParam(0));						
				}
				break;				
				case Action.ACTION_T_SET_UNIT_FIELD:
				{
					s.append("SET UNIT FIELD ");
					String f = "\"FIELD_" + action.getParam(0) + "\"";
					defines.define(Defines.DEFINE_FIELD, f, action.getParam(0));
					s.append(f);
					s.append(" TO ");
					s.append(action.getParam(1));
					s.append(" ON ");
					s.append(decompileTargetCode(action.getParam(2)));
				}
				break;				
				case Action.ACTION_T_SET_UNIT_FLAG:
				{
					s.append("SET UNIT FLAG TO ");
					StringBuffer flagsList = new StringBuffer();
					boolean hadFlag = false;
					for(int f=0;f<32;f++)
					{
						if((action.getParam(0) & (1<<f))>0)
						{
							if(hadFlag)
							{
								flagsList.append(", ");
							}
							String fl = "\"FLAG_" + Integer.toString((int)(1<<f)) + "\"";
							defines.define(Defines.DEFINE_FIELD, fl, action.getParam(0));
							flagsList.append(fl);
							hadFlag = true;
						}
					}
					s.append(flagsList);
					s.append(" TO ");
					s.append(action.getParam(1));
					s.append(" ON ");
					s.append(decompileTargetCode(action.getParam(2)));
				}
				break;
				case Action.ACTION_T_SOUND:
				{
					s.append("SOUND ");
					String e = "\"SOUND_" + action.getParam(0) + "\"";
					defines.define(Defines.DEFINE_SOUND, e, action.getParam(0));
					s.append(e);					
				}
				break;
				case Action.ACTION_T_RANDOM_SOUND:
				{
					s.append("SOUND ");
					String e = "\"SOUND_" + action.getParam(0) + "\"";
					defines.define(Defines.DEFINE_SOUND, e, action.getParam(0));
					s.append(e);
					if(action.getParam(1)!=-1)
					{
						s.append(" OR ");
						e = "\"SOUND_" + action.getParam(1) + "\"";
						defines.define(Defines.DEFINE_SOUND, e, action.getParam(1));
						s.append(e);
					}
					if(action.getParam(2)!=-1)
					{
						s.append(" OR ");
						e = "\"SOUND_" + action.getParam(2) + "\"";
						defines.define(Defines.DEFINE_SOUND, e, action.getParam(2));
						s.append(e);
					}
				}
				break;
				case Action.ACTION_T_SUMMON:
				{
					s.append("SUMMON ");
					String e = "\"CREATURE_" + action.getParam(0) + "\"";
					defines.define(Defines.DEFINE_CREATURE, e, action.getParam(0));
					s.append(e);
					s.append(" ");
					s.append(decompileTargetCode(action.getParam(1)));
					if(action.getParam(2)>0)
					{
						s.append(" FOR ");
						s.append(convertToTime(action.getParam(2)));
					}
				}
				break;
				case Action.ACTION_T_THREAT_ALL_PCT:
				{
					s.append("THREAT ALL ");
					s.append(convertToPercent(action.getParam(0)));
				}
				break;
				case Action.ACTION_T_THREAT_SINGLE_PCT:
				{
					s.append("THREAT SINGLE ");
					s.append(convertToPercent(action.getParam(0)));
					s.append(" ");
					s.append(decompileTargetCode(action.getParam(1)));
				}
				break;
			}
			s.append("\n");
		}
		return s.toString();
	}

	private String decompileTargetCode(int param) {
		switch(param)
		{
			case Target.TARGET_T_CASTER: return "TARGET CASTER";
			case Target.TARGET_T_HOSTILE: return "TARGET HOSTILE";
			case Target.TARGET_T_HOSTILE_LAST_AGGRO: return "TARGET HOSTILE LAST AGGRO";
			case Target.TARGET_T_HOSTILE_RANDOM: return "TARGET HOSTILE RANDOM";
			case Target.TARGET_T_HOSTILE_RANDOM_NOT_TOP: return "TARGET HOSTILE RANDOM NOT TOP";
			case Target.TARGET_T_HOSTILE_SECOND_AGGRO: return "TARGET HOSTILE SECOND AGGRO";
			case Target.TARGET_T_SELF: return "TARGET SELF";
		}
		return "UNKNOWN TARGET TYPE";
	}

	private String convertToTime(int param) {
		StringBuffer s = new StringBuffer();
		if(param%1000==0)
		{
			s.append(Integer.toString(param/1000));
			s.append(" s"); 
		} else {
			s.append(Integer.toString(param));
			s.append(" ms");
		}
		return s.toString();
	}

	private String convertToPercent(int param) {
		StringBuffer s = new StringBuffer();
		s.append(Integer.toString(param));
		s.append("%");
		return s.toString();
	}		
	
	private String getDecompiledText(int text)
	{
		StringBuffer result = new StringBuffer();
		LocalizedTexts t = (LocalizedTexts) session.get(LocalizedTexts.class, new Integer(text));
		if(t!=null)
		{
			result.append("\"");
			result.append(SQLBuilder.escapeString(t.getLocale0()));
			result.append("\"");
			if(t.getLocale2()!=null && t.getLocale2().length()>0)
			{
				result.append(",FRENCH=\"");
				result.append(SQLBuilder.escapeString(t.getLocale2()));
				result.append("\"");
			}
			if(t.getLocale3()!=null && t.getLocale3().length()>0)
			{
				result.append(",GERMAN=\"");
				result.append(SQLBuilder.escapeString(t.getLocale3()));
				result.append("\"");
			}
		} else {
			return "\"!!!!Undefined text!!!!\"";
		}
		return result.toString();
	}
}
